import json
import logging

import numpy as np
from django.http import HttpRequest
from django.http import StreamingHttpResponse
from django.views import View
from django.views.decorators import gzip

from algorithm import position, calibrate
from apps.device.Camera import CameraFactory, BaseCamera
from apps.device.models import Camera, CameraModel
from utils.Result import Result
from utils.UnSureJsonResponse import UnSureJsonResponse

logger = logging.getLogger('log')


def gen_display(camera):
    """视频流生成器功能。"""
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')


def gen_fisheye_display(camera, theta, fi, fov):
    """视频流生成器功能。"""
    while True:
        frame = camera.get_fisheye_frame(theta, fi, fov)
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')


class DisplayView(View):
    @staticmethod
    @gzip.gzip_page
    def get(request: HttpRequest):
        """
        视频流路由。将其放入img标记的src属性中。
        例如：<img src='https://ip:port/uri' >
        """
        try:
            camera_id = int(request.GET.get('camera_id'))
            base_camera = CameraFactory.get_camera(camera_id)
            if base_camera is None:
                return UnSureJsonResponse(Result.fail(code=3007, message='相机访问异常'))
            else:
                return StreamingHttpResponse(gen_display(base_camera),
                                             content_type='multipart/x-mixed-replace; boundary=frame')
        except (ValueError, TypeError) as error:
            logger.error(error)
            return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
        except Exception as error:
            logger.error(error)
            return UnSureJsonResponse(Result.fail(code=1005, message='接口调用失败'))

    @staticmethod
    @gzip.gzip_page
    def fisheye_camera(request: HttpRequest):
        """
        鱼眼展开页面路由
        """
        if request.method == 'GET':
            try:
                camera_id = int(request.GET.get('camera_id'))
                theta = int(request.GET.get('theta'))
                fi = int(request.GET.get('fi'))
                fov = int(request.GET.get('fov'))
                # 获取相机
                camera = Camera.objects.get(id=camera_id)
                if camera.camera_model.camera_type == 0:
                    # 是鱼眼相机，可正常使用
                    base_camera = CameraFactory.get_camera(camera_id)
                    if base_camera is None:
                        return UnSureJsonResponse(Result.fail(code=3007, message='相机访问异常'))
                    else:
                        return StreamingHttpResponse(gen_fisheye_display(base_camera, theta, fi, fov),
                                                     content_type='multipart/x-mixed-replace; boundary=frame')
                else:
                    # 不是鱼眼相机
                    return UnSureJsonResponse(Result.fail(code=4003, message='相机类型错误'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except Exception as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1005, message='接口调用失败'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))


class CalibrateView(View):
    """
    标定View
    """

    @staticmethod
    def inner_screenshot(request: HttpRequest):
        """
        内参截图
        内参标定，主要针对相机型号
        相机型号为逻辑型号，不存在具体实体，所以需要借助具体相机或者视频访问接口
        """
        if request.method == 'POST':
            param: dict = json.loads(request.body)
            camera_model_id = param.get('camera_model_id')
            camera_id = param.get('camera_id')
            camera_api = param.get('camera_api')
            try:
                camera_model_id = int(camera_model_id)
                # 判断型号ID是否存在
                CameraModel.objects.get(id=camera_model_id)
                if camera_id is not None:
                    # 相机ID已存在，使用实体相机作为标定工具
                    camera_id = int(camera_id)
                    camera = Camera.objects.get(id=camera_id)
                    if camera.camera_model.id != camera_model_id:
                        # 物理相机的型号ID与相机型号ID不匹配
                        return UnSureJsonResponse(Result.fail(code=4101, message='相机型号不匹配'))
                    else:
                        # 匹配
                        # 相机工厂获取相机视频类
                        base_camera = CameraFactory.get_camera(camera_id)
                        # 相机视频类为None，获取失败，参数异常
                        if base_camera is None:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                        else:
                            # 截图
                            path = base_camera.inner_screenshot()
                            return UnSureJsonResponse(Result.success(data=path))
                else:
                    # 不存在相机ID，通过临时api来进行标定
                    if camera_api is not None:
                        base_camera = CameraFactory.get_temp_camera(camera_api)
                        if base_camera is not None:
                            # 截图
                            path = base_camera.inner_screenshot()
                            return UnSureJsonResponse(Result.success(data=path))
                        else:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                    else:
                        return UnSureJsonResponse(Result.fail(code=1001, message='缺少必要参数'))
            except AttributeError as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except CameraModel.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3001, message='相机型号不存在'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))

    @staticmethod
    def inner_reset(request: HttpRequest):
        """
        内参截图重置
        内参标定，主要针对相机型号
        相机型号为逻辑型号，不存在具体实体，所以需要借助具体相机或者视频访问接口
        """
        if request.method == 'POST':
            param: dict = json.loads(request.body)
            camera_model_id = param.get('camera_model_id')
            camera_id = param.get('camera_id')
            camera_api = param.get('camera_api')
            try:
                camera_model_id = int(camera_model_id)
                # 判断型号ID是否存在
                CameraModel.objects.get(id=camera_model_id)
                if camera_id is not None:
                    # 相机ID已存在，使用实体相机作为标定工具
                    camera_id = int(camera_id)
                    camera = Camera.objects.get(id=camera_id)
                    if camera.camera_model.id != camera_model_id:
                        # 物理相机的型号ID与相机型号ID不匹配
                        return UnSureJsonResponse(Result.fail(code=4101, message='相机型号不匹配'))
                    else:
                        # 匹配
                        # 相机工厂获取相机视频类
                        base_camera = CameraFactory.get_camera(camera_id)
                        # 相机视频类为None，获取失败，参数异常
                        if base_camera is None:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                        else:
                            # 重置截图
                            base_camera.inner_reset()
                            return UnSureJsonResponse(Result.success())
                else:
                    # 不存在相机ID，通过临时api来进行标定
                    if camera_api is not None:
                        base_camera = CameraFactory.get_temp_camera(camera_api)
                        if base_camera is not None:
                            # 重置截图
                            base_camera.inner_reset()
                            return UnSureJsonResponse(Result.success())
                        else:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                    else:
                        return UnSureJsonResponse(Result.fail(code=1001, message='缺少必要参数'))
            except AttributeError as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except CameraModel.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3001, message='相机型号不存在'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))

    @staticmethod
    def inner(request: HttpRequest):
        """
        标定内参
        内参标定，主要针对相机型号
        相机型号为逻辑型号，不存在具体实体，所以需要借助具体相机或者视频访问接口
        """
        if request.method == 'POST':
            param: dict = json.loads(request.body)
            camera_model_id = param.get('camera_model_id')
            camera_id = param.get('camera_id')
            camera_api = param.get('camera_api')
            chessboard_row = param.get('chessboard_row')
            chessboard_col = param.get('chessboard_col')
            chessboard = (chessboard_row, chessboard_col)
            try:
                camera_model_id = int(camera_model_id)
                # 判断型号ID是否存在
                camera_model = CameraModel.objects.get(id=camera_model_id)
                if camera_id is not None:
                    # 相机ID已存在，使用实体相机作为标定工具
                    camera_id = int(camera_id)
                    camera = Camera.objects.get(id=camera_id)
                    if camera.camera_model.id != camera_model_id:
                        # 物理相机的型号ID与相机型号ID不匹配
                        return UnSureJsonResponse(Result.fail(code=4101, message='相机型号不匹配'))
                    else:
                        # 匹配
                        # 相机工厂获取相机视频类
                        base_camera = CameraFactory.get_camera(camera_id)
                        # 相机视频类为None，获取失败，参数异常
                        if base_camera is None:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                        else:
                            # 标定内参
                            if len(base_camera.inner_cameras) >= 5:
                                # 根据棋盘格角点规格，自动生成世界坐标
                                # 因为同类型相机的内参一般是固定的，所以得到内参和世界坐标规格无关，所以只需随机生成世界坐标即可进行内参标定
                                obj_points = calibrate.get_obj_points(chessboard)
                                # 修改obj_points的shape属性
                                obj_points = obj_points.reshape(1, -1, 3)

                                if camera_model.camera_type == 0:
                                    # 鱼眼相机
                                    ret, mtx, dist = calibrate.cal_internal_fisheye(obj_points,
                                                                                    base_camera.inner_cameras,
                                                                                    chessboard)
                                elif camera_model.camera_type == 1:
                                    # 单目相机
                                    ret, mtx, dist = calibrate.cal_internal_monocular(obj_points,
                                                                                      base_camera.inner_cameras,
                                                                                      chessboard)
                                else:
                                    return UnSureJsonResponse(Result.fail(code=3009, message='相机型号类型错误'))

                                if ret:
                                    # 转换内参
                                    camera_model.set_mtx(mtx)
                                    camera_model.set_dist(dist)
                                    camera_model.save()
                                    return UnSureJsonResponse(Result.success(data=camera_model, delete=['is_removed']))
                                else:
                                    return UnSureJsonResponse(Result.fail(code=4102, message='标定图片不足'))
                            else:
                                return UnSureJsonResponse(Result.fail(code=4103, message='标定内参失败'))
                else:
                    # 不存在相机ID，通过临时api来进行标定
                    if camera_api is not None:
                        base_camera = CameraFactory.get_temp_camera(camera_api)
                        if base_camera is not None:
                            # 标定内参
                            if len(base_camera.inner_cameras) >= 5:
                                # 根据棋盘格角点规格，自动生成世界坐标
                                # 因为同类型相机的内参一般是固定的，所以得到内参和世界坐标规格无关，所以只需随机生成世界坐标即可进行内参标定
                                obj_points = calibrate.get_obj_points(chessboard)
                                # 修改obj_points的shape属性
                                obj_points = obj_points.reshape(1, -1, 3)

                                if camera_model.camera_type == 0:
                                    # 鱼眼相机
                                    ret, mtx, dist = calibrate.cal_internal_fisheye(obj_points,
                                                                                    base_camera.inner_cameras,
                                                                                    chessboard)
                                elif camera_model.camera_type == 1:
                                    # 单目相机
                                    ret, mtx, dist = calibrate.cal_internal_monocular(obj_points,
                                                                                      base_camera.inner_cameras,
                                                                                      chessboard)
                                else:
                                    return UnSureJsonResponse(Result.fail(code=3009, message='相机型号类型错误'))

                                if ret:
                                    # 转换内参
                                    camera_model.set_mtx(mtx)
                                    camera_model.set_dist(dist)
                                    camera_model.save()
                                    return UnSureJsonResponse(Result.success(data=camera_model, delete=['is_removed']))
                                else:
                                    return UnSureJsonResponse(Result.fail(code=4103, message='标定内参失败'))
                            else:
                                return UnSureJsonResponse(Result.fail(code=4102, message='标定图片不足'))
                        else:
                            return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                    else:
                        return UnSureJsonResponse(Result.fail(code=1001, message='缺少必要参数'))
            except AttributeError as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except CameraModel.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3001, message='相机型号不存在'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))

    @staticmethod
    def marker_point(request: HttpRequest):
        """
        辅助标记点主要针对外参标定
        外参标定针对的是实体相机，所以需要相机具体相机ID
        """
        if request.method == 'POST':
            param: dict = json.loads(request.body)
            try:
                camera_id = int(param.get('camera_id'))
                camera = Camera.objects.get(id=camera_id)
                if camera.camera_model.check_camera_inner():
                    # 检验相机内参，外参的标定需要先标定内参
                    base_camera: BaseCamera = CameraFactory.get_camera(camera_id)
                    if base_camera is not None:
                        # 获取相机成功
                        img = base_camera.read()
                        while img is None:
                            img = base_camera.read()
                        marker_point = calibrate.get_auxiliary_marker_point(img)
                        return UnSureJsonResponse(Result.success(data=marker_point))
                    else:
                        # 获取相机失败
                        return UnSureJsonResponse(Result.fail(code=4002, message='相机获取失败'))
                else:
                    return UnSureJsonResponse(Result.fail(code=4104, message='内参未标定'))
            except AttributeError as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))

    @staticmethod
    def outer(request: HttpRequest):
        """
        外参标定
        camera_id：需要标定的相机ID
        obj_points：标定的世界坐标，shape：[N, 3]
        pix_points：标定的像素坐标，shape：[N, 2]
        """
        if request.method == 'POST':
            param: dict = json.loads(request.body)
            try:
                camera_id = int(param.get('camera_id'))
                obj_points = param.get('obj_points')
                pix_points = param.get('pix_points')
                camera = Camera.objects.get(id=camera_id)
                if camera.camera_model.check_camera_inner():
                    # 检验相机内参，外参的标定需要先标定内参
                    base_camera: BaseCamera = CameraFactory.get_camera(camera_id)
                    if base_camera is not None:
                        # 获取相机成功
                        img = base_camera.read()
                        while img is None:
                            img = base_camera.read()
                        if camera.camera_model.camera_type == 0:
                            # 鱼眼相机
                            ret, rvecs, rmat, tvecs = \
                                calibrate.cal_outside_punctuation_fisheye(np.float32(obj_points),
                                                                          np.float32(pix_points),
                                                                          camera.camera_model.get_mtx(),
                                                                          camera.camera_model.get_dist())
                        elif camera.camera_model.camera_type == 1:
                            # 单目相机
                            ret, rvecs, rmat, tvecs = \
                                calibrate.cal_outside_punctuation_monocular(np.float32(obj_points),
                                                                            np.float32(pix_points),
                                                                            camera.camera_model.get_mtx(),
                                                                            camera.camera_model.get_dist())
                        else:
                            return UnSureJsonResponse(Result.fail(code=3009, message='相机型号类型错误'))
                        if ret:
                            # 转换内参
                            camera.set_rvecs(rvecs)
                            camera.set_tvecs(tvecs)
                            camera.save()
                            return UnSureJsonResponse(Result.success(data=camera, delete=['is_removed']))
                        else:
                            return UnSureJsonResponse(Result.fail(code=4105, message='标定外参失败'))
                    else:
                        # 获取相机失败
                        return UnSureJsonResponse(Result.fail(code=4002, message='相机获取失败'))
                else:
                    return UnSureJsonResponse(Result.fail(code=4104, message='内参未标定'))
            except (ValueError, TypeError) as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except AttributeError as error:
                logger.error(error)
                return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))


class PositionView(View):
    """
    定位View
    """

    @staticmethod
    def location(request: HttpRequest):
        """
        相机的基本定位
        """
        if request.method == 'GET':
            try:
                camera_id = int(request.GET.get('camera_id'))
                pixel_point_x = float(request.GET.get('pixel_point_x'))
                pixel_point_y = float(request.GET.get('pixel_point_x'))
                camera: Camera = Camera.objects.get(id=camera_id)
                if camera is None:
                    # 获取相机失败
                    return UnSureJsonResponse(Result.fail(code=4001, message='获取相机失败'))
                else:
                    # 获取相机成功，验证相机参数
                    check = camera.check_camera()
                    if check:
                        # 初始化用于目标定位的np数组
                        position_ = np.zeros((1, 2))
                        # 数组赋值
                        position_[0][0] = pixel_point_x
                        position_[0][1] = pixel_point_y

                        ret, points = False, None
                        # 目标定位
                        if camera.camera_model.camera_type == 0:
                            # 鱼眼相机
                            ret, points = position.position_fisheye(position_, camera.camera_model.get_mtx(),
                                                                    camera.camera_model.get_dist(), camera.get_rmat(),
                                                                    camera.get_tvecs())
                        elif camera.camera_model.camera_type == 1:
                            # 单目相机
                            ret, points = position.position_monocular(position_, camera.camera_model.get_mtx(),
                                                                      camera.camera_model.get_dist(), camera.get_rmat(),
                                                                      camera.get_tvecs())
                        if ret:
                            data = {}
                            data.setdefault('position_point_x', points[0][0])
                            data.setdefault('position_point_y', points[0][1])
                            return UnSureJsonResponse(Result.success(data=data))
                        else:
                            return UnSureJsonResponse(Result.fail(code=4201, message='定位失败'))
                    else:
                        # 检验失败
                        return UnSureJsonResponse(Result.fail(code=3008, message='相机参数异常'))
            except (ValueError, TypeError):
                return UnSureJsonResponse(Result.fail(code=1006, message='参数格式错误'))
            except Camera.DoesNotExist:
                return UnSureJsonResponse(Result.fail(code=3101, message='相机不存在'))
        else:
            return UnSureJsonResponse(Result.fail(code=1004, message='请求方法错误'))
